/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.binding.util;

import java.awt.event.*;
import java.beans.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import ch.trackedbean.binding.action.*;

/**
 * Handler for {@link JList} actions which offers:<br>
 * <ul>
 * <li>A popupmenu</li>
 * <li>A default action (for double clicks)</li>
 * <li>Selection dependent actions (for buttons)</li>
 * </ul>
 * 
 * The {@link Action#setEnabled(boolean)} method of a dependent actions will be called with the result of {@link ISelectionAction#selectionChanged(List)} (using
 * the current selection).
 * 
 * @param <T> The content type of the list
 * 
 * @author M. Hautle
 */
public class ListSelectionHandler<T> {
    /** The registered selection actions. */
    private final List<ISelectionAction> selectionActions = new ArrayList<ISelectionAction>(5);

    /** The popupmanager. */
    private final LocalPopupManager popupManager = new LocalPopupManager();

    /** The selection and mouse handler. */
    private final Handler handler = new Handler();

    /** The default action or null. */
    private ISelectionAction<T> defaultAction;

    /** The list to wrap. */
    private final JList list;

    /**
     * Default constructor.
     * 
     * @param list The list to wrap
     */
    public ListSelectionHandler(JList list) {
        this.list = list;
        list.addListSelectionListener(handler);
        list.addMouseListener(handler);
        list.getModel().addListDataListener(handler);
        list.addPropertyChangeListener("model", handler);
    }

    /**
     * Returns the list.
     * 
     * @return The list
     */
    public JList getList() {
        return list;
    }

    /**
     * Returns the currently selected elements
     * 
     * @return The selected elements
     */
    @SuppressWarnings("unchecked")
    public List<T> getSelection() {
        if (list.isSelectionEmpty())
            return Collections.emptyList();
        final int[] sel = list.getSelectedIndices();
        final List<T> res = new ArrayList<T>(sel.length);
        final ListModel model = list.getModel();
        final int cnt = model.getSize();
        for (int i : sel) {
            if (i < cnt)
                res.add((T) model.getElementAt(i));
        }
        return res;
    }

    /**
     * Returns the default action.
     * 
     * @return The action or null
     */
    public ISelectionAction<T> getDefaultAction() {
        return defaultAction;
    }

    /**
     * Sets the default action of the list.
     * 
     * @param action The action or null
     */
    public void setDefaultAction(ISelectionAction<T> action) {
        this.defaultAction = action;
    }

    /**
     * Returns the popup manager.
     * 
     * @return The manager
     */
    public LocalPopupManager getPopupManager() {
        return popupManager;
    }

    /**
     * Adds the given action and calls {@link ISelectionAction#selectionChanged(List)} using the current selection.
     * 
     * @param action Adds the given action
     */
    public void addAction(ISelectionAction<T> action) {
        if (selectionActions.contains(action))
            return;
        selectionActions.add(action);
        action.setEnabled(action.selectionChanged(getSelection()));
    }

    /**
     * Removes the given action.
     * 
     * @param action Removes the given action
     */
    public void removeAction(ISelectionAction<T> action) {
        selectionActions.remove(action);
    }

    /**
     * "Destroys" the handler by freeing it's resources and detaching it's internal listeners
     */
    public void dispose() {
        selectionActions.clear();
        list.removeListSelectionListener(handler);
        list.removeMouseListener(handler);
        list.removePropertyChangeListener("model", handler);
        list.getModel().removeListDataListener(handler);
    }

    /**
     * The selection and mouse event handler. Not implemented by the main class to avoid exposing non API methods.
     * 
     * @author M. Hautle
     */
    private class Handler implements ListSelectionListener, MouseListener, ListDataListener, PropertyChangeListener {
        /**
         * {@inheritDoc}
         */
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            // model changed - swap the listener too
            ((ListModel) evt.getOldValue()).removeListDataListener(this);
            ((ListModel) evt.getNewValue()).addListDataListener(this);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void valueChanged(ListSelectionEvent e) {
            if (!e.getValueIsAdjusting())
                fireEvent();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void mouseClicked(MouseEvent e) {
            if (defaultAction == null || e.getClickCount() < 2)
                return;
            final List<T> sel = getSelection();
            if (sel.size() != 1)
                return;
            if (defaultAction.selectionChanged(sel))
                defaultAction.actionPerformed(new ActionEvent(list, 0, ""));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void mousePressed(MouseEvent e) {
            if (e.isPopupTrigger())
                showPopup(e);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void mouseReleased(MouseEvent e) {
            if (e.isPopupTrigger())
                showPopup(e);
        }

        /**
         * Fires a list selection event.
         */
        private void fireEvent() {
            final List<T> sel = getSelection();
            for (ISelectionAction<T> a : selectionActions)
                a.setEnabled(a.selectionChanged(sel));
        }

        /**
         * Shows the popup for the selection.
         * 
         * @param e The mouse event
         */
        private void showPopup(MouseEvent e) {
            final JPopupMenu popup = popupManager.getMenu(getSelection());
            if (popup.getComponentCount() > 0)
                popup.show(e.getComponent(), e.getX(), e.getY());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void intervalAdded(ListDataEvent e) {
            fireEvent();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void intervalRemoved(ListDataEvent e) {
            fireEvent();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void contentsChanged(ListDataEvent e) {
            fireEvent();
        }

        /**
         * Not implemented.
         * 
         * @param e Not used
         */
        @Override
        public void mouseEntered(MouseEvent e) {
        }

        /**
         * Not implemented.
         * 
         * @param e Not used
         */
        @Override
        public void mouseExited(MouseEvent e) {
        }
    }
}
